/*
 * Copyright 2014 Alibaba.com All right reserved. This software is the
 * confidential and proprietary information of Alibaba.com ("Confidential
 * Information"). You shall not disclose such Confidential Information and shall
 * use it only in accordance with the terms of the license agreement you entered
 * into with Alibaba.com.
 */
package com.alibaba.wallstreet.uitls;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPFileFilter;
import org.apache.commons.net.ftp.parser.EnterpriseUnixFTPEntryParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.wallstreet.base.FtpListType;
import com.alibaba.wallstreet.exception.BizCommonException;
import com.alibaba.wallstreet.exception.CommonErrorCode;
import com.alibaba.wallstreet.threadfactory.NamedThreadFactory;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.ChannelSftp.LsEntry;
import com.jcraft.jsch.ChannelSftp.LsEntrySelector;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;

/**
 * 类AbstractSFTPDownloadBankDriverImpl.java的实现描述：TODO 类实现描述
 * 
 * @author moxian.zl 2014-3-4 下午4:26:58
 */
public class SFTPClient {

    private static Logger                   logger           = LoggerFactory.getLogger(SFTPClient.class);

    private String                          host;
    private int                             port;
    private String                          identityFilePath;
    private String                          loginName;
    private String                          password;
    private String                          srcFileSeparator = "/";

    private static ThreadLocal<ChannelSftp> sftpLocal        = new ThreadLocal<ChannelSftp>();
    private static ThreadLocal<Session>     sshSessionLocal  = new ThreadLocal<Session>();

    public String getSrcFileSeparator() {
        return srcFileSeparator;
    }

    public void setSrcFileSeparator(String srcFileSeparator) {
        this.srcFileSeparator = srcFileSeparator;
    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public String getIdentityFilePath() {
        return identityFilePath;
    }

    public void setIdentityFilePath(String identityFilePath) {
        this.identityFilePath = identityFilePath;
    }

    public String getLoginName() {
        return loginName;
    }

    public void setLoginName(String loginName) {
        this.loginName = loginName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public List<String> listPath(String path, final String regex) {
        return listPath(path, regex, FtpListType.ALL);
    }

    public List<String> listPath(String path, final String regex, final FtpListType listType) {
        final List<String> result = new ArrayList<String>();
        try {
            ChannelSftp sftp = sftpLocal.get();
            sftp.ls(path, new LsEntrySelector() {

                @Override
                public int select(LsEntry entry) {
                    logger.info("select_file: " + entry.getFilename());
                    if (regex == null || entry.getFilename().matches(regex)) {
                        // 如果只显示文件时过滤文件夹
                        if (listType == FtpListType.FILE_ONLY && entry.getAttrs().isDir()) {
                            return CONTINUE;
                        }
                        // 只显示目录时过滤文件
                        if (listType == FtpListType.DIRECTORY_ONLY && !entry.getAttrs().isDir()) {
                            return CONTINUE;
                        }
                        result.add(entry.getFilename());
                    }
                    return CONTINUE;
                }
            });
            return result;
        } catch (SftpException e) {
            throw new BizCommonException("List path " + path + " failure", e, CommonErrorCode.SYSTEM_EXCEPTION);
        }
    }

    public void cd(String path) {
        try {
            ChannelSftp sftp = sftpLocal.get();
            sftp.cd(path);
        } catch (SftpException e) {
            throw new BizCommonException("Cd to path " + path + " failure", e, CommonErrorCode.SYSTEM_EXCEPTION);
        }
    }

    public void lcd(String path) {
        try {
            ChannelSftp sftp = sftpLocal.get();
            sftp.lcd(path);
        } catch (SftpException e) {
            throw new BizCommonException("Lcd to path " + path + " failure", e, CommonErrorCode.SYSTEM_EXCEPTION);
        }
    }

    public List<String> get(String srcPath, List<String> srcFileNames, String destPath, int threadMaxDownloadNum,
                            int maxThreadNum) {
        if (srcFileNames == null || srcFileNames.size() <= threadMaxDownloadNum) {
            return get(srcPath, srcFileNames, destPath);
        }
        ExecutorService executorService = Executors.newFixedThreadPool(maxThreadNum,
                                                                       new NamedThreadFactory("SFTP_Downloader"));
        List<String> filesToGet = new ArrayList<String>();
        List<Future<List<String>>> futures = new ArrayList<Future<List<String>>>();
        for (String srcFileName : srcFileNames) {
            filesToGet.add(srcFileName);
            if (filesToGet.size() >= threadMaxDownloadNum) {
                futures.add(executorService.submit(new SFTPGetCallable(srcPath, filesToGet, destPath)));
                filesToGet = new ArrayList<String>();
            }
        }
        // 仍然剩余未下载的文件
        if (filesToGet.size() > 0) {
            futures.add(executorService.submit(new SFTPGetCallable(srcPath, filesToGet, destPath)));
            filesToGet = null;
        }
        executorService.shutdown();
        // 等待文件下载完成
        try {
            while (!executorService.awaitTermination(100L, TimeUnit.MILLISECONDS)) {
            }
        } catch (InterruptedException e) {
            logger.error(e.getMessage(), e);
        }
        // 获得文件下载结果
        List<String> result = new ArrayList<String>();
        for (Future<List<String>> future : futures) {
            List<String> files;
            try {
                files = future.get();
            } catch (Exception e) {
                throw new BizCommonException("Download file failure with multiple threads", e,
                                             CommonErrorCode.SYSTEM_EXCEPTION);
            }
            if (files != null && !files.isEmpty()) {
                result.addAll(files);
            }
        }
        return result;

    }

    private class SFTPGetCallable implements Callable<List<String>> {

        private final List<String> filesToGet;
        private final String       srcPath;
        private final String       destPath;

        public SFTPGetCallable(String srcPath, List<String> filesToGet, String destPath){
            this.srcPath = srcPath;
            this.filesToGet = filesToGet;
            this.destPath = destPath;
        }

        @Override
        public List<String> call() throws Exception {
            try {
                connect();
                return get(srcPath, filesToGet, destPath);
            } finally {
                disconnect();
            }

        }
    }

    public List<String> get(String srcPath, List<String> srcFileNames, String destPath) {
        if (srcFileNames == null) {
            logger.info("srcFileNames_is_null");
            return null;
        }
        List<String> result = new ArrayList<String>();
        for (String srcFileName : srcFileNames) {
            result.add(get(srcPath, srcFileName, destPath));
        }
        return result;
    }

    public String get(String srcPath, String srcFileName, String destPath) {
        String srcFile = getActualSrcPath(srcPath, srcFileName);
        String destFile = getActualDestPath(destPath, srcFileName);
        try {
            File destDict = new File(destFile).getParentFile();
            if (!destDict.exists()) {
                destDict.mkdirs();
            }
            ChannelSftp sftp = sftpLocal.get();
            logger.debug("Begin to get file : {}", srcFile);
            sftp.get(srcFile, destFile);
            logger.debug("Success get : {} to {}", srcFile, destFile);
            return destFile;
        } catch (SftpException e) {
            throw new BizCommonException("Can not get " + srcFile + " to " + destFile, e,
                                         CommonErrorCode.SYSTEM_EXCEPTION);
        }
    }

    protected String getActualSrcPath(String srcPath, String srcFileName) {
        return (srcPath == null || srcPath.isEmpty() ? "" : srcPath + srcFileSeparator) + srcFileName;
    }

    protected String getActualDestPath(String destPath, String fileName) {
        return (destPath == null || destPath.isEmpty() ? "" : destPath + File.separator) + fileName;
    }

    public void connect() {
        try {
            JSch jsch = new JSch();
            Session sshSession = jsch.getSession(loginName, host, port);
            if (identityFilePath != null) {
                jsch.addIdentity(identityFilePath);
            }
            if (password != null) {
                sshSession.setPassword(password);
            }
            sshSession.setConfig("StrictHostKeyChecking", "no");
            sshSession.connect();
            ChannelSftp sftp = (ChannelSftp) sshSession.openChannel("sftp");
            sftp.connect();
            sftpLocal.set(sftp);
            sshSessionLocal.set(sshSession);
            logger.info("sftp: connect to " + host + ":" + port + " with login name : " + loginName + " success");
        } catch (Exception e) {
            throw new BizCommonException("Can not connect to " + host + ":" + port + " with login name : " + loginName,
                                         e, CommonErrorCode.SYSTEM_EXCEPTION);
        }
    }

    public void disconnect() {
        ChannelSftp sftp = sftpLocal.get();
        if (sftp != null) {
            sftp.disconnect();
            sftpLocal.remove();
        }
        Session sshSession = sshSessionLocal.get();
        if (sshSession != null) {
            sshSession.disconnect();
            sshSessionLocal.remove();
        }
    }

    public static void main(String[] args) throws Exception {
        // SFTPBankDriver sftp = new SFTPBankDriver();
        // // sftp.setHost("10.125.1.72");
        // // sftp.setLoginName("wallstreet");
        // // sftp.setPort(22);
        // // sftp.setPassword("9527");
        // // sftp.setHost("116.12.250.239");
        // // sftp.setLoginName("HKABB01");
        // // sftp.setPort(8122);
        // //
        // sftp.setIdentityFilePath("C:\\Users\\moxian.zl\\AppData\\Roaming\\SSH\\UserKeys\\key_gang.ang_FIS-BG-TEST-SSH");
        // sftp.setHost("ftp.qualityassurance.cegateway2.hsbc.com.hk");
        // sftp.setLoginName("ftvu0232");
        // sftp.setPort(38026);
        // sftp.setPassword("King8745");
        // sftp.connect();
        // System.out.println(sftp.listPath("/Inbox/", ".*"));
        FtpClient ftpsClient = new FtpClient("203.112.87.66", 38026, "TLS");
        // ftpsClient.setUserName("ftvu0232");
        // ftpsClient.setPassWord("King8745");
        ftpsClient.login("ftvu0232", "King8745");
        ftpsClient.protWithP();
        ftpsClient.enterLocalPassiveMode();
        // System.out.println(ftpsClient.getWorkingDirectory());
        for (String file : ftpsClient.listFiles(null, EnterpriseUnixFTPEntryParser.class, new FTPFileFilter() {

            @Override
            public boolean accept(FTPFile file) {
                if (file.getRawListing().startsWith("-ART--UN")) {
                    return false;
                }
                return true;
            }
        })) {
            System.out.println(file);
        }

        ftpsClient.get("D:\\STMTHK.CJ071.031216194632.PGP", "/ftvu0232/STMTHK.CJ071.031216194632.PGP");
        int a = 1;
        if (a == 1) {
            ftpsClient.quit();
            return;
        }
        // String srcPath = "/home/wallstreet/test/zhuli/files/file";
        // List<String> files = sftp.listPath(srcPath, ".*pgp$");
        // long time = System.currentTimeMillis();
        // List<String> destFiles = sftp.get(srcPath, files,
        // "C:\\Users\\moxian.zl\\Desktop\\网关\\银行接口文档\\文件格式\\dbs\\test\\%TODAY%\\d1");
        // System.out.println(System.currentTimeMillis() - time);
        // printFileInfo(destFiles);
        // time = System.currentTimeMillis();
        // List<String> destFiles2 = sftp.get(srcPath, files,
        // "C:\\Users\\moxian.zl\\Desktop\\网关\\银行接口文档\\文件格式\\dbs\\test\\%TODAY%\\d2",
        // 50, 8);
        // System.out.println(System.currentTimeMillis() - time);
        // printFileInfo(destFiles2);
        // sftp.disconnect();
    }

    // private static void printFileInfo(List<String> destFiles) {
    // System.out.print("File nums " + destFiles.size());
    // long total = 0;
    // for (String destFile : destFiles) {
    // File dest = new File(destFile);
    // total += dest.length();
    // }
    // System.out.println(" ,total size : " + total);
    // }
}
